package com.vortex.cloud.zhsw.xinyang.util;

import com.google.common.collect.Lists;
import com.vortex.cloud.lbs.enums.ShapeTypeEnum;
import com.vortex.cloud.zhsw.xinyang.enums.SquarePrefixEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.geotools.geometry.jts.JTS;
import org.geotools.referencing.CRS;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import org.springframework.util.Assert;
import si.uom.SI;

import javax.measure.MetricPrefix;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import java.util.Objects;

/**
 * JTS工具类
 *
 * @author huohao@vortexinfo.cn
 * @date 2022/3/10 9:07
 */
@Slf4j
public class JtsUtils {
    public static final String COMMA = ",";

    public static final int TWO = 2;
    public static final int THREE = 3;


    public static final String SEMICOLON = ";";

    /**
     * 判断点是否在范围内
     *
     * @param lng           点经度
     * @param lat           点纬度
     * @param shapeType     形状类型
     * @param lngLats       范围就经纬度
     * @param limitDistance 允许的误差
     * @return java.lang.Boolean
     * @author huohao@vortexinfo.cn
     */
    public static Boolean isWithinDistance(Double lng, Double lat, ShapeTypeEnum shapeType, String lngLats, Double limitDistance) {
        if (Objects.isNull(lng) || Objects.isNull(lat) || Objects.isNull(shapeType) || StringUtils.isBlank(lngLats)) {
            return false;
        }

        GeometryFactory geometryFactory = new GeometryFactory();
        Geometry point = geometryFactory.createPoint(new Coordinate(lng, lat));
        Geometry geometry = null;

        if (Objects.isNull(limitDistance)) {
            limitDistance = 0D;
        }

        switch (shapeType) {
            case POINT:
                geometry = geometryFactory.createPoint(transferLngLat(lngLats));
                break;
            case CIRCLE:
                String[] lngLatArray = lngLats.split(COMMA);
                Assert.isTrue(lngLatArray.length == THREE, "圆形只能为经度,纬度,半径的格式");
                String lngString = lngLatArray[0];
                String latString = lngLatArray[1];
                String radiusString = lngLatArray[2];
                Assert.isTrue(NumberUtils.isParsable(lngString)
                        && NumberUtils.isParsable(latString)
                        && NumberUtils.isParsable(radiusString), "经度,纬度,半径只是能是数字");
                double geoLng = NumberUtils.toDouble(lngString);
                double geoLat = NumberUtils.toDouble(latString);
                geometry = geometryFactory.createPoint(new Coordinate(geoLng, geoLat));

                double radius = NumberUtils.toDouble(radiusString);
                limitDistance = new BigDecimal(limitDistance)
                        .add(new BigDecimal(radius))
                        .setScale(THREE, RoundingMode.DOWN)
                        .doubleValue();
                break;
            case LINE:
            case POLYLINE:
                geometry = geometryFactory.createLineString(transferLngLats(lngLats));
                break;
            case RECTANGLE:
            case POLYGON:
                Coordinate[] coordinates = transferLngLats(lngLats);
                geometry = geometryFactory.createPolygon(closeCoordinates(coordinates));
                break;
            default:
                break;
        }

        return isWithinDistance(point, geometry, limitDistance);
    }


    /**
     * 计算面积
     *
     * @param geometry     经纬度
     * @param squarePrefix 单位-默认m {@link SquarePrefixEnum}
     * @param scale        保留小数点
     * @param roundingMode 保留方式 {@link RoundingMode}
     * @return java.lang.Double
     * @author huohao@vortexinfo.cn
     */
    public static Double getArea(Geometry geometry, SquarePrefixEnum squarePrefix, Integer scale, RoundingMode roundingMode) {
        try {
            // WGS84(一般项目中常用的是CSR:84和EPSG:4326)
            CoordinateReferenceSystem sourceCrs = CRS.decode("CRS:84");
            // Pseudo-Mercator(墨卡托投影)
            CoordinateReferenceSystem targetCrs = CRS.decode("EPSG:3857");
            MathTransform transform = CRS.findMathTransform(sourceCrs, targetCrs, false);
            Geometry geometryMercator = JTS.transform(geometry, transform);
            // 平方米
            double area = SI.SQUARE_METRE.getConverterTo(SI.SQUARE_METRE.prefix(Objects.nonNull(squarePrefix) ? squarePrefix : SquarePrefixEnum.M_SQUARE)).convert(geometryMercator.getArea());
            if (Objects.isNull(scale)) {
                return area;
            }
            return BigDecimal.valueOf(area).setScale(scale, Objects.nonNull(roundingMode) ? roundingMode : RoundingMode.HALF_UP).doubleValue();
        } catch (FactoryException | TransformException e) {
            log.error("JtsUtils.getArea(Geometry geometry,SquarePrefixEnum squarePrefix, Integer scale, RoundingMode roundingMode) error", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 计算面积
     *
     * @param geometry     经纬度
     * @param metricPrefix {@link MetricPrefix}
     * @param scale        保留小数点
     * @param roundingMode 保留方式 {@link RoundingMode}
     * @return java.lang.Double
     * @author huohao@vortexinfo.cn
     */
    public static Double getLength(Geometry geometry, MetricPrefix metricPrefix, Integer scale, RoundingMode roundingMode) {
        double length = geometry.getLength();
        if (Objects.nonNull(metricPrefix)) {
            length = SI.METRE.getConverterTo(SI.METRE.prefix(metricPrefix)).convert(length);
        }
        if (Objects.isNull(scale)) {
            return length;
        }
        return BigDecimal.valueOf(length).setScale(scale, Objects.nonNull(roundingMode) ? roundingMode : RoundingMode.HALF_UP).doubleValue();
    }


    /**
     * 判断点是否在范围内
     *
     * @param g1       点位
     * @param g2       范围
     * @param distance 允许误差
     * @return java.lang.Boolean
     * @author huohao@vortexinfo.cn
     */
    private static Boolean isWithinDistance(Geometry g1, Geometry g2, Double distance) {
        try {
            // WGS84，地理坐标系
            CoordinateReferenceSystem sourceCrs = CRS.decode("CRS:84");
            // Pseudo-Mercator(墨卡托投影)，投影坐标系
            CoordinateReferenceSystem targetCrs = CRS.decode("EPSG:3857");
            // 坐标系转换
            MathTransform transform = CRS.findMathTransform(sourceCrs, targetCrs, false);
            Geometry g1Geo = JTS.transform(g1, transform);
            Geometry g2Geo = JTS.transform(g2, transform);
            return g1Geo.isWithinDistance(g2Geo, distance);
        } catch (Exception e) {
            log.error("JtsUtils.isWithinDistance(Geometry g1, Geometry g2, Double distance) error", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 字符串转Geometry
     *
     * @param shapeType 图形类型 {@link ShapeTypeEnum}
     * @param lngLats   经纬度（限定wgs84）
     * @return org.locationtech.jts.geom.Geometry
     * @author huohao@vortexinfo.cn
     */
    public static Geometry transferToGeometry(ShapeTypeEnum shapeType, String lngLats) {
        GeometryFactory geometryFactory = new GeometryFactory();
        Geometry geometry = null;
        switch (shapeType) {
            case POINT:
                geometry = geometryFactory.createPoint(transferLngLat(lngLats));
                break;
            case CIRCLE:
                String[] lngLatArray = lngLats.split(COMMA);
                Assert.isTrue(lngLatArray.length == THREE, "圆形只能为经度,纬度,半径的格式");
                String lngString = lngLatArray[0];
                String latString = lngLatArray[1];
                String radiusString = lngLatArray[2];
                Assert.isTrue(NumberUtils.isParsable(lngString)
                        && NumberUtils.isParsable(latString)
                        && NumberUtils.isParsable(radiusString), "经度,纬度,半径只是能是数字");
                double geoLng = NumberUtils.toDouble(lngString);
                double geoLat = NumberUtils.toDouble(latString);
                geometry = geometryFactory.createPoint(new Coordinate(geoLng, geoLat));
                break;
            case LINE:
            case POLYLINE:
                geometry = geometryFactory.createLineString(transferLngLats(lngLats));
                break;
            case RECTANGLE:
            case POLYGON:
                Coordinate[] coordinates = transferLngLats(lngLats);
                geometry = geometryFactory.createPolygon(closeCoordinates(coordinates));
                break;
            default:
                break;
        }
        return geometry;

    }

    /**
     * 获取封闭经纬度图元
     *
     * @param coordinates 经纬度数组
     * @return org.locationtech.jts.geom.Coordinate[]
     * @author huohao@vortexinfo.cn
     */
    private static Coordinate[] closeCoordinates(Coordinate[] coordinates) {
        Coordinate first = coordinates[0];
        Coordinate last = coordinates[coordinates.length - 1];
        if (first.getX() == last.getX() && first.getY() == last.getY()) {
            return coordinates;
        } else {
            List<Coordinate> newCoordinates = Lists.newArrayList(coordinates);
            newCoordinates.add(new Coordinate(first.getX(), first.getY()));
            return newCoordinates.toArray(new Coordinate[0]);
        }
    }

    /**
     * 经纬度转Coordinate数组
     *
     * @param lngLats 经纬度(多个;分割)
     * @return org.locationtech.jts.geom.Coordinate[]
     * @author huohao@vortexinfo.cn
     */
    private static Coordinate[] transferLngLats(String lngLats) {
        List<Coordinate> coordinates = Lists.newArrayList();
        for (String lngLatStr : lngLats.split(SEMICOLON)) {
            coordinates.add(transferLngLat(lngLatStr));
        }

        return coordinates.toArray(new Coordinate[0]);
    }

    /**
     * 经纬度转Coordinate（单个）
     *
     * @param lngLat 经纬度(单个)
     * @return org.locationtech.jts.geom.Coordinate
     * @author huohao@vortexinfo.cn
     */
    private static Coordinate transferLngLat(String lngLat) {
        String[] lngLatArray = lngLat.split(COMMA);
        Assert.isTrue(lngLatArray.length == TWO, "经纬度只能为经度,纬度的格式");
        String lngString = lngLatArray[0];
        String latString = lngLatArray[1];
        Assert.isTrue(NumberUtils.isParsable(lngString) && NumberUtils.isParsable(latString), "经度,纬度只是能是数字");
        double lng = NumberUtils.toDouble(lngString);
        double lat = NumberUtils.toDouble(latString);

        return new Coordinate(lng, lat);
    }


}
